/*
 * CommitmentAction.java   ver    4 Nov 2009
 * 
 * Copyright (c) 2009 mentalsmash.org
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of the Apache License does not indicate that this project is
 * affiliated with the Apache Software Foundation.
 */
package org.mentalsmash.tazio.transactions.control.impl;

import org.mentalsmash.tazio.transactions.control.CommitmentAction;
import org.mentalsmash.tazio.transactions.control.CommitmentTask;
import org.mentalsmash.tazio.transactions.control.TazioTransaction;
import org.mentalsmash.tazio.transactions.control.TransactionStateException;
import org.mentalsmash.tazio.transactions.control.ValidationAgent;
import org.mentalsmash.tazio.transactions.model.RootSet;
import org.mentalsmash.tazio.transactions.rm.RMTransactionsFacade;
import org.mentalsmash.tazio.transactions.user.RollbackException;
import org.mentalsmash.tazio.transactions.user.TransactionStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * A <tt>CommitmentAction</tt> is a callable class, that actually implements the
 * action of committing a {@link TazioTransactionImpl}.
 * <p>
 * This class should not be directly used. Use {@link CommitmentTask} instead/
 * </p>
 * 
 * @author Andrea Reale
 * @version 0.0.1
 */
class WRMCommitmentAction extends CommitmentAction {

    final static Logger log = LoggerFactory
	    .getLogger(WRMCommitmentAction.class);

    private final WRMTransactionManager tm;

    private final ValidationAgent localValidationAgent;

    private final RMTransactionsFacade replicaManager;

    public WRMCommitmentAction(final TazioTransaction toCommit,
	    final WRMTransactionManager tm, final RootSet rs) {
	super(toCommit, rs);
	this.tm = tm;
	this.localValidationAgent = tm.getValidationAgent();
	this.replicaManager = tm.getReplicaManagerFacade();
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.util.concurrent.Callable#call()
     */
    @Override
    public TransactionStatus call() throws Exception {
	
	if (this.toCommit.getStatus() != TransactionStatus.RUNNING) {
	    log.error("Transaction" + this.toCommit
		    + " that wasn't in the RUNNING status was asked to commit");
	    throw new TransactionStateException("Transaction " + this.toCommit
		    + " was not in the RUNNING status");
	}

	if (this.toCommit.isRollbackOnly()) {
	    this.toCommit.stateTransition(TransactionStatus.ROLLED_BACK);
	    log.debug("Transaction {} rolled back", this.toCommit);
	    throw new RollbackException("Transaction " + this.toCommit
		    + " was set to rollback only");
	}

	// only a thread at a time can be validating, no matter the order
	this.tm.getValidationLock().lock();

	try {
	    log.trace("{} passed in the LOCAL_VALIDATION phase", this.toCommit);
	    // local validation
	    this.toCommit.stateTransition(TransactionStatus.LOCAL_VALIDATION);
	    if (!this.localValidationAgent.validate(this.toCommit)) {

		this.toCommit.stateTransition(TransactionStatus.ROLLED_BACK);
		log.debug("Transaction {} rolled back", this.toCommit);
		throw new RollbackException(
			"Local validation failed. Transaction " + this.toCommit
				+ " rolled back");
	    }
	} finally {
	    this.tm.getValidationLock().unlock();
	}

	// global validation
	this.toCommit.stateTransition(TransactionStatus.GLOBAL_VALIDATION);
	final long finalSeqNo = this.replicaManager
		.commitTransaction(this.toCommit); // remote

	if (finalSeqNo < 0) {
	    this.toCommit.stateTransition(TransactionStatus.ROLLED_BACK);
	    log.debug("Transaction {} rolled back", this.toCommit);
	    throw new RollbackException(
		    "Global validation failed. Transaction " + this.toCommit
			    + " rolled back");
	}

	log.trace("{} passed the global validation", this.toCommit);

	// only a thread at a time. The order should not matter.. does it?
	this.tm.getChangesLock().lock();
	try {
	    log.trace("{} is applying its changes to the root set",
		    this.toCommit);
	    this.toCommit.setSeqNo(finalSeqNo);
	    doApplyChanges();
	    this.toCommit.stateTransition(TransactionStatus.COMMITTED);
	} finally {
	    this.tm.getChangesLock().unlock();
	}
	log.trace("{} commited", this.toCommit);

	return this.toCommit.getStatus();
	

    }
}
